{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CW-Lite/Pro Glitch Exploration\n",
    "\n",
    "CW-Husky has an internal logic analyzer that is handy for visualizing and validating Husky's glitch generation logic (see `husky_glitch.ipynb`).\n",
    "\n",
    "CW-lite/pro don't have this internal logic analyzer... but if you have a CW-Husky, you can use *its* logic analyzer to look at the CW-lite/pro-generated glitches! It's a bit hacky but it works.\n",
    "\n",
    "To do this, use jumper cables to connect these signals between the Husky and CW-lite/pro (on their 20-pin connectors):\n",
    "- Husky's HS1 to CW-lite/pro's HS2\n",
    "- Husky's HS2 to CW-lite/pro's HS1\n",
    "- a Husky ground pin to a CW-lite/pro ground pin\n",
    "\n",
    "The glitch signals captured in this notebook are digital signals; if you're interested in the actual shape of the glitch output, you won't get that from this; you'll need a good analog oscilloscope instead.\n",
    "\n",
    "No target needs to be connected for this notebook.\n",
    "\n",
    "This is also a companion to test_s6_glitch.py, for when visual inspection of glitches is needed.\n",
    "\n",
    "This notebook assumes familiarity with what's already covered in `husky_glitch.ipynb`; go through that one first.\n",
    "\n",
    "After you've wired your two ChipWhisperers together, connect them to your computer. If they are the only two ChipWhisperers you have connected, the commands below should establish the connection to each CW without any errors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPE=\"OPENADC\"\n",
    "PLATFORM=\"CWLITEARM\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "hscope = cw.scope(name='Husky')\n",
    "\n",
    "# use 'Lite' or 'Pro' as appropriate:\n",
    "scope = cw.scope(name='Lite')\n",
    "#scope = cw.scope(name='Pro')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set up Husky:\n",
    "\n",
    "In our setup, Husky drives the clock which is used by the \"target\" CW (lite/pro) as its glitch source clock.\n",
    "\n",
    "The Husky logic analyzer is triggered by *its own* glitch -- not the target CW's glitch -- because calling `hscope.glitch.manual_trigger()` on Husky gives us a handy mechanism to trigger the capture.\n",
    "\n",
    "The target CW glitch is set to \"continuous\", so we can see the target glitches even if we're not triggering from them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hscope.clock.clkgen_src = 'system'\n",
    "hscope.clock.clkgen_freq = 10e6\n",
    "hscope.glitch.enabled = True\n",
    "hscope.glitch.clk_src = 'pll'\n",
    "hscope.LA.enabled = True\n",
    "hscope.LA.clkgen_enabled = True\n",
    "hscope.LA.oversampling_factor = 50\n",
    "hscope.LA.capture_group = 'CW 20-pin'\n",
    "hscope.LA.capture_depth = 512\n",
    "hscope.LA.trigger_source = \"glitch_source\"\n",
    "hscope.io.hs2 = 'clkgen'\n",
    "hscope.glitch.trigger_src = 'manual'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set up target CW (lite/pro):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.clk_src = 'target'\n",
    "scope.glitch.repeat = 1\n",
    "scope.glitch.output = 'glitch_only'\n",
    "#scope.glitch.trigger_src = 'manual'\n",
    "scope.glitch.trigger_src = 'continuous'\n",
    "scope.glitch.offset = 10\n",
    "scope.glitch.width = 25\n",
    "scope.glitch.repeat = 10\n",
    "scope.io.hs2 = 'glitch'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.resetDCMs()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert hscope.LA.locked"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single capture:\n",
    "\n",
    "Let's first do a simple single capture.\n",
    "\n",
    "We can pick arbitrary glitch `offset`, `offset_fine`, `width`, and `width_fine` parameters; start with the values below, then explore other values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.offset = 0.8\n",
    "scope.glitch.width = 15\n",
    "scope.glitch.width_fine = 0\n",
    "scope.glitch.offset_fine = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hscope.LA.arm()\n",
    "hscope.glitch.manual_trigger()\n",
    "raw = hscope.LA.read_capture_data()\n",
    "glitchout = hscope.LA.extract(raw, 4)\n",
    "source    = hscope.LA.extract(raw, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "\n",
    "import numpy as np\n",
    "output_notebook(INLINE)\n",
    "\n",
    "o = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(source)))\n",
    "O1 = o.line(xrange, source + 2, line_color='black')\n",
    "O2 = o.line(xrange, glitchout + 0, line_color='purple', line_width=2)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='source clock', renderers=[O1]),\n",
    "    LegendItem(label='glitch clock output', renderers=[O2]),\n",
    "])\n",
    "o.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(o)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive glitch visualization: coarse offset and width\n",
    "\n",
    "Now we step through many coarse width/offset combinations so that we can interactively plot them.\n",
    "\n",
    "Adjust INCR, START and STOP if desired."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "INCR = 1\n",
    "START = -48\n",
    "STOP = 48\n",
    "STEPS = len(range(START, STOP, INCR)) + 1\n",
    "\n",
    "allglitchouts = np.zeros((STEPS, STEPS, hscope.LA.capture_depth))\n",
    "allsources    = np.zeros((STEPS, STEPS, hscope.LA.capture_depth))\n",
    "actual_offsets = []\n",
    "\n",
    "from tqdm.notebook import tnrange\n",
    "\n",
    "scope.glitch.offset = START\n",
    "scope.glitch.width = START\n",
    "\n",
    "scope.glitch.offset_fine = 0\n",
    "scope.glitch.width_fine = 0\n",
    "\n",
    "for i, o in enumerate(tnrange(START, STOP, INCR)):\n",
    "    if not o: continue\n",
    "    scope.glitch.offset = float(o)\n",
    "    actual_offsets.append(scope.glitch.offset)\n",
    "    for j, w in enumerate(range(START, STOP, INCR)):\n",
    "        if not w: continue\n",
    "        scope.glitch.width = float(w)\n",
    "        hscope.LA.arm()\n",
    "        hscope.glitch.manual_trigger()\n",
    "        raw = hscope.LA.read_capture_data()\n",
    "        allglitchouts[i][j] = hscope.LA.extract(raw, 4)\n",
    "        allsources[i][j]    = hscope.LA.extract(raw, 5)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_plot(offset, width):\n",
    "    S1.data_source.data['y'] = allsources[offset][width] + 2\n",
    "    S2.data_source.data['y'] = allglitchouts[offset][width] + 0\n",
    "    push_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import interact, Layout\n",
    "from bokeh.io import push_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "\n",
    "o = 0\n",
    "w = 0\n",
    "\n",
    "S = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(allsources[o][w])))\n",
    "S1 = S.line(xrange, allsources[o][w] + 2, line_color='black')\n",
    "S2 = S.line(xrange, allglitchouts[o][w] + 0, line_color='purple', line_width=2)\n",
    "\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='Husky source clock', renderers=[S1]),\n",
    "    LegendItem(label='CW-lite glitch clock output', renderers=[S2]),\n",
    "])\n",
    "S.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_plot, offset=(0, STEPS-1), width=(0, STEPS-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive glitch visualization: fine offset and width\n",
    "\n",
    "Now we step through many fine width/offset combinations so that we can interactively plot them.\n",
    "\n",
    "Adjust STEPS if desired, but at 50x oversampling, smaller fine phase adjustments than the defaults here can't be observed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# set coarse settings to what you want:\n",
    "scope.glitch.width = 49.803\n",
    "scope.glitch.offset = 45"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Loop over fine offsets:\n",
    "STEPS = 31\n",
    "INCR = 511 // STEPS\n",
    "\n",
    "allglitchouts = np.zeros((STEPS, STEPS, hscope.LA.capture_depth))\n",
    "allsources    = np.zeros((STEPS, STEPS, hscope.LA.capture_depth))\n",
    "\n",
    "from tqdm.notebook import tnrange\n",
    "\n",
    "#scope.glitch.offset = 1.0\n",
    "#scope.glitch.width = 25.0\n",
    "\n",
    "scope.glitch.offset_fine = -255\n",
    "scope.glitch.width_fine = -255\n",
    "\n",
    "for o in tnrange(STEPS):\n",
    "    scope.glitch.width_fine = -255\n",
    "    for w in range(STEPS):\n",
    "        hscope.LA.arm()\n",
    "        hscope.glitch.manual_trigger()\n",
    "        raw = hscope.LA.read_capture_data()\n",
    "        allglitchouts[o][w] = hscope.LA.extract(raw, 4)\n",
    "        allsources[o][w]    = hscope.LA.extract(raw, 5)\n",
    "        scope.glitch.width_fine += INCR\n",
    "    scope.glitch.offset_fine += INCR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import interact, Layout\n",
    "from bokeh.io import push_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "\n",
    "o = 0\n",
    "w = 0\n",
    "\n",
    "S = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(allsources[o][w])))\n",
    "S1 = S.line(xrange, allsources[o][w] + 2, line_color='black')\n",
    "S2 = S.line(xrange, allglitchouts[o][w] + 0, line_color='purple', line_width=2)\n",
    "\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='Husky source clock', renderers=[S1]),\n",
    "    LegendItem(label='CW-lite glitch clock output', renderers=[S2]),\n",
    "])\n",
    "S.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_plot, offset=(0, STEPS-1), width=(0, STEPS-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Validation: \"Double Glitches\" bug\n",
    "\n",
    "In running the captures above, you will have gotten a bunch of these warnings:\n",
    "\n",
    "`WARNING:ChipWhisperer Glitch:Negative offsets <-45 may result in double glitches!`\n",
    "\n",
    "This \"double glitches\" refers to the situation where `scope.glitch.repeat = ` but yet **two** glitches are produced.\n",
    "\n",
    "This is a [known issue](https://github.com/newaetech/chipwhisperer/issues/261) which is due to [clock domain crossings](https://en.wikipedia.org/wiki/Clock_domain_crossing). If you're experienced with digital design and multiple clocks, you will appreciate the challenge here: there are several clocks involved in the creation of glitches, and for certain width/offet settings, the relationship of these clocks makes it hard to avoid setup/hold violations. In our case here, the end result is that sometimes, the internal \"glitch enable\" signal which controls the number of glitches can sometimes last two clock cycles instead of one, which leads to the \"double glitch\" bug.\n",
    "\n",
    "ChipWhisperer software warns that double glitches are possible whenever `scope.glitch.offset < -45`, but we can use this setup to find *precisely* which settings result in double glitches. It turns out that, *for a particular FPGA bitfile*, double glitches are highly reproducible. The settings below give double glitches for the FPGA bitfiles used at the time of this writing; if the bitfiles are updated in the future, different results may be obtained. The `test_S6_glitch.py` script can be used to hunt for double glitch parameters.\n",
    "\n",
    "We need to change a couple of things in our capture setup: we make the target CW output the \"glitch enable\" signal instead of the glitch itself, and we make the Husky logic analyzer trigger on HS1 (the \"glitch enable\") instead; `manual_trigger()` is then called on the *target* CW to trigger the capture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.output = 'enable_only'\n",
    "scope.glitch.trigger_src = 'manual'\n",
    "scope.glitch.repeat = 1\n",
    "hscope.LA.trigger_source = \"HS1\"\n",
    "hscope.LA.oversampling_factor = 40"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single capture to show a double glitch:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.offset_fine = 0\n",
    "scope.glitch.width_fine = 0\n",
    "\n",
    "scope.glitch.offset = -49.0\n",
    "#scope.glitch.offset = 10.0\n",
    "scope.glitch.width = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hscope.LA.arm()\n",
    "scope.glitch.manual_trigger()\n",
    "raw = hscope.LA.read_capture_data()\n",
    "glitchout = hscope.LA.extract(raw, 4)\n",
    "source    = hscope.LA.extract(raw, 5)\n",
    "glitchlen = len(np.where(glitchout > 0)[0])\n",
    "oversamp = hscope.LA.oversampling_factor\n",
    "cycles = glitchlen/oversamp\n",
    "\n",
    "assert abs(glitchlen - oversamp) < oversamp / 4, \"Double glitch! Glitch enable seen high for %f cycles\" % cycles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should get the assertion error with the default settings. If you change `scope.glitch.offset` to something else (e.g. 10), the assertion should then pass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "import numpy as np\n",
    "output_notebook(INLINE)\n",
    "\n",
    "o = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(source)))\n",
    "o.line(xrange, source + 2, line_color='black')\n",
    "o.line(xrange, glitchout + 0, line_color='purple', line_width=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(o)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example of looping over several offsets to find which result in double glitches:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "glitches = 1\n",
    "oversamp = 20\n",
    "fine_step = 5\n",
    "desc = ''\n",
    "\n",
    "scope.glitch.repeat = glitches\n",
    "failing_offsets = []\n",
    "maxwidth = 0\n",
    "\n",
    "#scope.glitch.output = 'glitch_only'\n",
    "scope.glitch.output = 'enable_only'\n",
    "scope.glitch.trigger_src = 'manual'\n",
    "scope.glitch.repeat = 1\n",
    "hscope.LA.trigger_source = \"HS1\"\n",
    "hscope.LA.oversampling_factor = oversamp\n",
    "\n",
    "maxwidth = 0\n",
    "good = 0\n",
    "bad = 0\n",
    "failing_offsets = []\n",
    "\n",
    "for i in range(4):\n",
    "    offset_coarse = -49 + i*0.5\n",
    "    scope.glitch.offset = offset_coarse\n",
    "    for offset_fine in range(-255, 255, fine_step):\n",
    "        scope.glitch.offset_fine = offset_fine\n",
    "        if not scope.glitch.offset_fine == offset_fine:\n",
    "            continue\n",
    "        hscope.LA.arm()\n",
    "        scope.glitch.manual_trigger()\n",
    "        raw = hscope.LA.read_capture_data()\n",
    "        glitchout = hscope.LA.extract(raw, 4)\n",
    "        glitchlen = len(np.where(glitchout > 0)[0])\n",
    "        cycles = glitchlen/oversamp\n",
    "\n",
    "        if glitchlen and (abs(glitchlen/glitches - oversamp) > oversamp/4):\n",
    "            bad += 1\n",
    "            failing_offsets.append([scope.glitch.offset, offset_fine])\n",
    "            if glitchlen > maxwidth:\n",
    "                maxwidth = glitchlen\n",
    "        elif glitchlen:\n",
    "            good += 1\n",
    "\n",
    "if not failing_offsets:\n",
    "    print(\"No double glitches seen!\")\n",
    "else:\n",
    "    for fail in failing_offsets:\n",
    "        print(\"Double glitch with coarse offset: %f, fine offset: %d\" % (fail[0], fail[1]))\n",
    "    print(\"Maximum glitch length seen (in cycles): %f\" % (maxwidth/oversamp))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (venv37)",
   "language": "python",
   "name": "venv37"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
